<?php

namespace app\install\controller;

use think\facade\View;
use app\manage\model\Admin;
use buwang\util\Sql;
use buwang\util\File;

class Index extends BaseInstall
{
    /**
     * 安装日志文件名称
     * @var string
     */
    protected $install_log_file_name = 'install.log'; //安装日志所在地
    protected $config = [];//安装配置参数
    protected $source_file = '../app/install/source/env.template';//源配置文件
    protected $install_sql_file = "../app/install/source/database.sql";//安装sql所在文件\
    protected $target_dir = "../"; //安装目标文件夹
    protected $target_file = ".env"; //覆盖的配置文件名

    /**
     *安装
     */
    public function index()
    {
        if (!defined('__ROOT__')) define('__ROOT__', str_replace(['/index.php', '/install.php'], '', request()->root(true)));
        //如果已安装，请求重定向到主页
        if (file_exists($this->lock_file)) return "<h1>您已完成安装,如需重新安装请删除站点目录下的install.lock文件</h1>";

        $step = input("step", 1);

        //第一步，确定协议
        if ($step == 1) {
            return View::fetch('index/step-1', [], $this->replace);
        } else if ($step == 2) {
            //第二步，环境检测
            View::assign("continue", $this->envCheck());
            return View::fetch('index/step-2', [], $this->replace);
        } else if ($step == 3) {
            // 3.参数配置
            return View::fetch('index/step-3', [], $this->replace);
        } else if ($step == 4) {
            file_put_contents($this->install_log_file_name, '');//清空日志
            set_time_limit(300);
            //数据库
            $this->config['database_port'] = input("dbport", "3306");
            $this->config['database_host'] = input("dbhost", "localhost");
            $this->config['database_user'] = input("dbuser", "root");
            $this->config['database_password'] = input("dbpwd", "root");
            $this->config['database_name'] = input("dbname", "");//数据库名称
            $this->config['database_prefix'] = input("dbprefix", "bw_") ?: 'bw_';//前缀
            $this->config['database_charset'] = input("charset", "utf8");

            //redis
            $this->config['redis_port'] = input("rport", "6379");
            $this->config['redis_host'] = input("rhost", "127.0.0.1");
            $this->config['redis_password'] = input("rpwd", "");
            $this->config['redis_name'] = input("rname", 0);
            $this->config['redis_prefix'] = input('rprefix', "bwsaas"); //redis前缀

            //队列
            $this->config['queue_store'] = 'redis';

            //平台
            $this->config['user_name'] = input('username', "");
            $this->config['user_password'] = input('password', "");
            $this->config['user_password_repeat'] = input('password2', "");
            $this->config['have_city'] = input('city', "");// 是否导入城市数据

            if ($this->config['database_host'] == '' || $this->config['database_user'] == '') {
                return $this->returnError([], '数据库链接配置信息丢失!');
            }
            //平台
            if ($this->config['user_name'] == '' || $this->config['user_password'] == '') {
                return $this->returnError([], '平台信息不能为空!');
            }
            if (!preg_match("/^[a-zA-Z\d_]{7,}$/", $this->config['user_password'])) return $this->returnError([], '密码请使至少8位不能带中文和特殊符号');
            if (!preg_match("/^[a-zA-Z\d_]{5,}$/", $this->config['user_name'])) return $this->returnError([], '用户名请使至少5位不能带中文和特殊符号');
            if ($this->config['user_password'] != $this->config['user_password_repeat']) {
                return $this->returnError([], '两次密码输入不一样，请重新输入');
            }
            if ($this->config['database_prefix'] == '') {
                return $this->returnError([], '数据表前缀为空!');
            }
            try {
                //覆盖静态资源
                $this->coverStaticResource();
                //修改安装配置
                $this->setConfig();
                //执行安装sql
                $this->runInstallSql();
                //插入管理员
                $this->addAdmin();
                //写入安装文件
                $this->addLock();
            } catch (\Throwable $e) {
                return $this->returnError([], $e->getMessage());
            }
            return $this->returnSuccess([], "安装成功");
        }
    }

    public function installSuccess()
    {
        return View::fetch('index/step-4', [], $this->replace);
    }

    /**
     * 测试数据库
     */
    public function testDb($dbhost = '', $dbport = '', $dbuser = '', $dbpwd = '', $dbname = '')
    {
        $dbport = input("dbport", "");
        $dbhost = input("dbhost", "");
        $dbuser = input("dbuser", "");
        $dbpwd = input("dbpwd", "");
        $dbname = input("dbname", "");
        try {
            //默认端口3306
            if ($dbport != "" && $dbhost != "") {
                $dbhost = $dbport != '3306' ? $dbhost . ':' . $dbport : $dbhost;
            }
            if ($dbhost == '' || $dbuser == '')
                return $this->returnError([
                    "status" => -1,
                    "message" => "数据库账号或密码不能为空"
                ]);
            //进行数据库连接（忽略错误）
            $conn = @mysqli_connect($dbhost, $dbuser, $dbpwd);
            if ($conn) {
                if (empty($dbname)) {
                    $result = [
                        "status" => 1,
                        "message" => "数据库连接成功"
                    ];

                } else {
                    //进行数据库选择（忽略错误）
                    if (@mysqli_select_db($conn, $dbname)) {
                        $result = [
                            "status" => 2,
                            "message" => "数据库【{$dbname}】已存在，系统将覆盖数据库"
                        ];
                    } else {
                        $result = [
                            "status" => 2,
                            "message" => "数据库【{$dbname}】不存在,系统将自动创建"
                        ];

                    }
                }
            } else {
                $result = [
                    "status" => -1,
                    "message" => "数据库连接失败！"
                ];

            }
            @mysqli_close($conn);
            return $this->returnSuccess($result);
        } catch (\Exception $e) {
            $result = [
                "status" => -1,
                "message" => "数据库连接失败，请检查mysql服务是否启动"
            ];
            return $this->returnSuccess($result);
        }
    }


    /**
     * 测试redis数据库
     */
    public function testRDb($rhost = '', $rport = '', $rpwd = '', $rname = '')
    {
        $rport = input("rport", "");
        $rhost = input("rhost", "");
        $rpwd = input("rpwd", "");
        $rname = input("rname", "");
        try {
            if (!$rname) $rname = 0;
            if ($rhost == '') {
                return $this->returnError([
                    "status" => -1,
                    "message" => "数据库账号或密码不能为空"
                ]);
            }

            //进行数据库连接
            $redis = new \Redis();
            //连接
            $redis->connect($rhost, $rport);

            if ($rpwd) $redis->auth($rpwd);
            if ($redis->ping()) {
                //进行数据库选择（忽略错误）
                if ($redis->select($rname)) {
                    $result = [
                        "status" => 1,
                        "message" => "数据库可用"
                    ];
                } else {
                    $result = [
                        "status" => -1,
                        "message" => "数据库不存在"
                    ];
                }

            } else {
                $result = [
                    "status" => -1,
                    "message" => "数据库连接失败！"
                ];

            }
            $redis->close();
            return $this->returnSuccess($result);
        } catch (\Throwable $e) {
            $result = [
                "status" => -1,
                "message" => "redis连接失败，请检查redis服务是否启动！"
            ];
            return $this->returnSuccess($result);
        }
    }


    function str_replace_first($search, $replace, $subject)
    {
        return implode($replace, explode($search, $subject, 2));
    }


    private static function getPublicFiles($pathName, &$file_map)
    {
        //将结果保存在result变量中
        $result = array();
        $temp = array();
        //判断传入的变量是否是目录
        if (!is_dir($pathName) || !is_readable($pathName)) {
            return null;
        }
        //取出目录中的文件和子目录名,使用scandir函数
        $allFiles = scandir($pathName);
        //遍历他们
        foreach ($allFiles as $fileName) {
            //判断是否是.和..因为这两个东西神马也不是。。。
            if (in_array($fileName, array('.', '..'))) {
                continue;
            }
            //路径加文件名
            $fullName = $pathName . '/' . $fileName;
            //如果是目录的话就继续遍历这个目录
            if (is_dir($fullName)) {
                //将这个目录中的文件信息存入到数组中
                $result[$fullName] = self::getPublicFiles($fullName, $file_map);
            } else {
                //如果是文件就先存入临时变量
                $temp[] = $fullName;
                $pathinfo = pathinfo($fullName);
                $pathinfo['path'] = $fullName;
                $file_map[] = $pathinfo;
            }
        }
        //取出文件
        if ($temp) {
            foreach ($temp as $f) {
                $result[] = $f;
            }
        }
        return $result;
    }


    public function process()
    {
        if (request()->isGet()) {
            return View::fetch('');
        }
        if (request()->isPost()) {

            if (!file_exists($this->lock_file)) {
                $info = file_get_contents($this->install_log_file_name);
                $arr = explode(PHP_EOL, $info);
                return $this->returnSuccess($arr);
            } else {
                return $this->returnError([], '安装完成！');
            }


        }
    }

    /**系统环境检测
     * @return bool
     */
    protected function envCheck()
    {
        //系统变量
        $system_variables = [];
        $phpv = phpversion(); //得到php版本
        $os = PHP_OS;         //当前操作系统
        $server = $_SERVER['SERVER_SOFTWARE']; //使用的web服务器名称
        //取当前用户主机地址
        $host = (empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_HOST'] : $_SERVER['REMOTE_ADDR']);
        $name = $_SERVER['SERVER_NAME'];//服务器主机的名称
        //版本比较
        $verison = version_compare(PHP_VERSION, '7.2.0') == -1 ? false : true; //php版本是否支持
        //判断pdo扩展是否已安装
        $pdo = extension_loaded('pdo') && extension_loaded('pdo_mysql');
        $system_variables[] = ["name" => "pdo", "need" => "开启", "status" => $pdo];
        //判断curl扩展是否已安装
        $curl = extension_loaded('curl') && function_exists('curl_init');
        $system_variables[] = ["name" => "curl", "need" => "开启", "status" => $curl];
        //openssl扩展是否已安装
        $openssl = extension_loaded('openssl');
        $system_variables[] = ["name" => "openssl", "need" => "开启", "status" => $openssl];
        //gd扩展是否已安装
        $gd = extension_loaded('gd');
        $system_variables[] = ["name" => "GD库", "need" => "开启", "status" => $gd];
        //判断是否安装了redis扩展
        $redis = extension_loaded('redis');
        $system_variables[] = ["name" => "redis", "need" => "开启", "status" => $redis];

        //fileinfo扩展是否已安装
        $fileinfo = extension_loaded('fileinfo');
        $system_variables[] = ["name" => "fileinfo", "need" => "开启", "status" => $fileinfo];
        //获得系统根目录
        $root_path = str_replace("\\", DIRECTORY_SEPARATOR, dirname(dirname(dirname(dirname(__FILE__)))));
        $root_path = str_replace("/", DIRECTORY_SEPARATOR, $root_path);
        //目录列表
        $dirs_list = array(
            array("path" => $root_path, "path_name" => "/", "name" => "整目录"),
            array("path" => $root_path . DIRECTORY_SEPARATOR . "config", "path_name" => "config", "name" => "config"),
            array("path" => $root_path . DIRECTORY_SEPARATOR . "public", "path_name" => "public", "name" => "public"),
            array("path" => $root_path . DIRECTORY_SEPARATOR . 'runtime', "path_name" => "runtime", "name" => "runtime"),
            array("path" => $root_path . DIRECTORY_SEPARATOR . 'app/install', "path_name" => "app/install", "name" => "安装目录"),
        );
        //目录 可读 可写检测
        foreach ($dirs_list as $k => $v) {
            $is_readable = is_readable($v["path"]); //文件是否可读。
            $is_write = is_write($v["path"]); //文件是否可写。
            $dirs_list[$k]["is_readable"] = $is_readable; //记录可读状态
            $dirs_list[$k]["is_write"] = $is_write; //记录可写状态
        }
        View::assign("root_path", $root_path);//系统根目录
        View::assign("system_variables", $system_variables); //php扩展开启状态
        View::assign("phpv", $phpv);  //php版本
        View::assign("server", $server);  //使用的web服务器名称
        View::assign("host", $host); //获取当前用户主机地址
        View::assign("os", $os);//当前操作系统
        View::assign("name", $name); //服务器域名
        View::assign("verison", $verison); //php版本支持情况
        View::assign("dirs_list", $dirs_list); //硬盘是否可读可写
        //是否允许继续安装
        if ($verison && $pdo && $curl && $openssl && $gd && $fileinfo && $redis) {
            $continue = true;//允许
        } else {
            $continue = false;//不允许
        }
        return $continue;
    }


    /**覆盖与替换静态资源
     * @param $dir
     */
    protected function coverStaticResource()
    {
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 检测需要安装的静态文件' . PHP_EOL, FILE_APPEND);
        $file_map = [];//需要覆盖的静态文件
        //得到所有需要覆盖的静态文件
        self::getPublicFiles('../app/install/source/public', $file_map);
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 安装静态文件' . PHP_EOL, FILE_APPEND);
        foreach ($file_map as $file_path) {
            //目标静态文件所在全路径
            $push_path = str_replace('../app/install/source/public', '../public', $file_path['path']);
            //目标静态文件所在文件夹
            $file_dir = str_replace('../app/install/source/public', '../public', $file_path['dirname']);
            //检查写入权限
            $write_result = is_write($file_dir);
            if (!$write_result) {
                //判断是否有可写的权限，linux操作系统要注意这一点，windows不必注意。
                throw new \Exception("{$file_dir}文件夹不可写，权限不够!");
            }
            //得到模板内容
            $fp = fopen($file_path['path'], "r");
            $templateStr = fread($fp, filesize($file_path['path']));
            fclose($fp);
            //进行变量替换得到
            $templateStr = str_replace('{BW:WEBNAME}', __ROOT__, $templateStr);
            //执行文件写入
            $fp = fopen($push_path, "w");
            if ($fp == false) {
                throw new \Exception("写入配置失败，请检查{$push_path}是否可写入！");
            }
            fwrite($fp, $templateStr);
            fclose($fp);
        }
    }


    /**写入安装配置
     * @return \think\Response
     */
    protected function setConfig()
    {
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '检测 配置config.php文件读写权限' . PHP_EOL, FILE_APPEND);
        //可写测试
        $write_result = is_write($this->target_dir);
        if (!$write_result) {
            //判断是否有可写的权限，linux操作系统要注意这一点，windows不必注意。
            throw new \Exception('配置文件不可写，权限不够!');
        }
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 修改config配置' . PHP_EOL, FILE_APPEND);
        //修改配置文件
        $fp = fopen($this->source_file, "r");
        $configStr = fread($fp, filesize($this->source_file));
        fclose($fp);
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 安装基本配置' . PHP_EOL, FILE_APPEND);
//            return $this->returnError([], $configStr);
        $configStr = str_replace('{BW:HOSTNAME}', $this->config['database_host'], $configStr);
        $configStr = str_replace('{BW:DATABASE}', $this->config['database_name'], $configStr);
        $configStr = str_replace("{BW:USERNAME}", $this->config['database_user'], $configStr);
        $configStr = str_replace("{BW:PASSWORD}", $this->config['database_password'], $configStr);
        $configStr = str_replace("{BW:HOSTPORT}", $this->config['database_port'], $configStr);
        $configStr = str_replace("{BW:PREFIX}", $this->config['database_prefix'], $configStr);
        //写入redis
        $configStr = str_replace("{BW:REDIS_HOST}", $this->config['redis_host'], $configStr);
        $configStr = str_replace("{BW:REDIS_PORT}", $this->config['redis_port'], $configStr);
        $configStr = str_replace("{BW:REDIS_PASSWORD}", $this->config['redis_password'], $configStr);
        $configStr = str_replace("{BW:REDIS_SELECT}", $this->config['redis_name'], $configStr);
        $configStr = str_replace("{BW:REDIS_PREFIX}", $this->config['redis_prefix'], $configStr);
        //队列
        $configStr = str_replace("{BW:QUEUE_DEFAULT}", $this->config['queue_store'], $configStr);
        //会话
        $configStr = str_replace("{BW:SESSION_STORE}", 'file', $configStr);//TODO:20201125 jyk session 改成 文件
        $fp = fopen($this->target_dir . DIRECTORY_SEPARATOR . $this->target_file, "w");
        if ($fp == false) {
            throw new \Exception("写入配置失败，请检查{$this->target_dir}/{$this->target_file}是否可写入！");
        }
        fwrite($fp, $configStr);
        fclose($fp);
    }


    /**
     * 运行安装sql
     */
    protected function runInstallSql()
    {
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '检测 Redis和数据库连接' . PHP_EOL, FILE_APPEND);
        //数据库 和 redis 检测
        try {
            $conn = SQL::mysqlConnect($this->config['database_host'], $this->config['database_user'], $this->config['database_password']);
            //进行数据库连接
            $redis = SQL::redisConnect($this->config['redis_host'], $this->config['redis_port'], $this->config['redis_name'], $this->config['redis_password']);
        } catch (\Throwable $e) {
            throw new \Exception('检测 Redis和数据库连接失败：' . $e->getMessage());
        }

        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 删除数据库' . PHP_EOL, FILE_APPEND);
        //数据库可写和是否存在测试
        $empty_db = mysqli_select_db($conn, $this->config['database_name']);
        if ($empty_db) {
            $sql = "DROP DATABASE `{$this->config['database_name']}`";
            $retval = mysqli_query($conn, $sql);
            if (!$retval) {
                throw new \Exception('删除数据库失败: ' . mysqli_error($conn));
            }
        }
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 创建数据库' . PHP_EOL, FILE_APPEND);
        //如果数据库不存在，我们就进行创建。
        $dbsql = "CREATE DATABASE `{$this->config['database_name']}` DEFAULT CHARACTER SET {$this->config['database_charset']} COLLATE {$this->config['database_charset']}_general_ci";
        $db_create = mysqli_query($conn, $dbsql);
        if (!$db_create) {
            throw new \Exception('创建数据库失败，请确认该数据库用户是否有足够的权限!');
        }
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 连接数据库' . PHP_EOL, FILE_APPEND);
        //链接数据库
        @mysqli_select_db($conn, $this->config['database_name']);

        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 安装SQL语句' . PHP_EOL, FILE_APPEND);
        //导入SQL并执行。
        //设置数据库name
        @mysqli_query($conn, "SET NAMES {$this->config['database_charset']}");
        //提取并切割sql,并执行前缀替换
        $sql_statement = Sql::getSqlFromFile($this->install_sql_file);
        $count = count($sql_statement);//安装条数
        if (!empty($sql_statement)) {
            //修改表前缀
            if (!empty($this->config['database_prefix'])) {
                $sql_statement = str_replace('bw_', $this->config['database_prefix'], $sql_statement); //替换前缀 兼容老版本
                $sql_statement = str_replace('__BWPREFIX__', $this->config['database_prefix'], $sql_statement); //替换前缀
            }
            foreach ($sql_statement as $k => $statement) {
                //得到当前比例
                $speed = bcmul(bcdiv($k, $count, 4), 100, 2);
                file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . "-安装SQL执行进度：{$speed}%" . PHP_EOL, FILE_APPEND);//写日志
                @mysqli_query($conn, $statement);
            }
        }
        //插入城市sql
        if ($this->config['have_city']) {
            //导入SQL并执行。
            $city_sql_file_path = root_path() . DS . 'app' . DS . 'manage' . DS . 'source' . DS . 'city' . DS . 'datebase.sql';
            //提取并切割sql,并执行前缀替换
            $city_sql_statement = Sql::getSqlFromFile($city_sql_file_path);
            $city_count = count($city_sql_statement);//城市SQL安装条数
            if (!empty($city_sql_statement)) {
                //修改表前缀
                if (!empty($this->config['database_prefix'])) {
                    $city_sql_statement = str_replace('bw_', $this->config['database_prefix'], $city_sql_statement); //替换前缀 兼容老版本
                    $city_sql_statement = str_replace('__BWPREFIX__', $this->config['database_prefix'], $city_sql_statement); //替换前缀
                }
                foreach ($city_sql_statement as $ck => $city_statement) {
                    //得到当前比例
                    $city_speed = bcmul(bcdiv($ck, $city_count, 4), 100, 2);
                    file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . "-城市数据SQL执行进度：{$city_speed}%" . PHP_EOL, FILE_APPEND);//写日志
                    @mysqli_query($conn, $city_statement);
                }
            }
        }
        //关闭安装sql的会话
        @mysqli_close($conn);
    }

    /** 添加管理员
     * @throws \Exception
     */
    protected function addAdmin()
    {
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 管理员创建' . PHP_EOL, FILE_APPEND);
        //重新连接带指定数据库的sql会话
        $conn = SQL::mysqlConnect($this->config['database_host'], $this->config['database_user'], $this->config['database_password'], $this->config['database_name']);
        //得到密码
        $pass = Admin::getPassword($this->config['user_password']);
        $password = $pass['password'];
        $salt = $pass['salt'];
        //admin更新语句
        $update_admin_sql = "
            UPDATE `{$this->config['database_prefix']}admin` SET `id`='1', `username`='{$this->config['user_name']}', `nickname`='Admin', `password`='{$password}', `salt`='{$salt}', `avatar`='', `mobile`='', `email`='admin@admin.com', `loginfailure`='0', `logintime`='1604383988', `loginip`='39.96.151.170', `create_time`='1492186163', `update_time`='1591582951', `status`='1' WHERE (`id`='1')
            ";
        mysqli_query($conn, $update_admin_sql);
        @mysqli_close($conn);
    }

    /**
     * 添加lock文件
     */
    protected function addLock()
    {
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 安装检测' . PHP_EOL, FILE_APPEND);
        $fp = fopen($this->lock_file, "w");
        if ($fp == false) {
            throw new \Exception("写入失败，请检查目录" . dirname(dirname(__FILE__)) . "是否可写入！'");
        }
        fwrite($fp, '已安装');
        fclose($fp);
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '执行 安装完成！！！！' . PHP_EOL, FILE_APPEND);
        file_put_contents($this->install_log_file_name, date('Y-m-d H:i:s', time()) . '-' . '若页面连接超时，请手动刷新页面！！！！' . PHP_EOL, FILE_APPEND);
    }
}